The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.
This protocol describes a network analysis workflow in Cytoscape for a set of differentially expressed genes. Points covered:
- Retrieving relevant networks from public databases
- Network functional enrichment analysis
- Integration and visualization of experimental data
- Exporting network visualizations
Installation
if (!requireNamespace("BiocManager", quietly = TRUE)){
install.packages("BiocManager")
}
if(!"RCy3" %in% installed.packages()){
BiocManager::install("RCy3")
}
library(RCy3)
Getting started
First, launch Cytoscape and keep it running whenever using RCy3. Confirm that you have everything installed and running:
cytoscapePing()
cytoscapeVersionInfo()
Prerequisites
If you haven’t already, install the STRINGapp
Background
Ovarian serous cystadenocarcinoma is a type of epithelial ovarian cancer which accounts for ~90% of all ovarian cancers. The data used in this protocol are from The Cancer Genome Atlas, in which multiple subtypes of serous cystadenocarcinoma were identified and characterized by mRNA expression.
We will focus on the differential gene expression between two subtypes, Mesenchymal and Immunoreactive.
For convenience, the data has already been analyzed and pre-filtered, using log fold change value and adjusted p-value.
Network Retrieval
Many public databases and multiple Cytoscape apps allow you to retrieve a network or pathway relevant to your data. For this workflow, we will use the STRING app. Some other options include:
Retrieve Networks from STRING
To identify a relevant network, we will query the STRING database in two different ways:
- Query STRING protein with the list of differentially expressed genes.
- Query STRING disease for a keyword; ovarian cancer.
The two examples are split into two separate workflows below.
Example 1: STRING Protein Query Up-regulated Genes
Load the file containing the differential gene expression data and filter for up-regulated genes, TCGA-Ovarian-MesenvsImmuno_data.csv:
de.genes <- read.table("https://raw.githubusercontent.com/cytoscape/cytoscape-tutorials/gh-pages/protocols/data/TCGA-Ovarian-MesenvsImmuno_data.csv", header = TRUE, sep = ",", quote="\"", stringsAsFactors = FALSE)
de.genes.up <- de.genes[which(de.genes$logFC >= 1.8 & de.genes$FDR.adjusted.Pvalue <= 0.05),]
We will use the identifiers in the first column of this datafile to run a STRING protein query, with confidence (score) cutoff of 0.4:
string.cmd = paste('string protein query query="', paste(de.genes.up$Gene, collapse = '\n'), '" cutoff=0.4 species="Homo sapiens"', sep = "")
commandsRun(string.cmd)
The resulting network will load automatically and contains up-regulated genes recognized by STRING, and interactions between them with an evidence score of 0.4 or greater.
The networks consists of one large connected component, several smaller networks, and some unconnected nodes. We will select only the connected nodes to work with for the rest of this tutorial, by creating a subnetwork based on all edges:
createSubnetwork(edges='all', subnetwork.name='String de genes up')
Data Integration
Next we will create a visualization from the log fold changes and p-values from our TCGA dataset. Since the STRING network is a protein-protein network, it is annotated with protein identifiers (Uniprot and Ensembl protein), as well as HGNC gene symbols. Our data from TCGA has NCBI Gene identifiers (formerly Entrez), so before importing the data we are going to use the ID Mapper functionality in Cytoscape to map the network to NCBI Gene.
mapped.cols <- mapTableColumn('display name', 'Human', 'HGNC', 'Entrez Gene')
We can now integrate the same differential gene expression data with the network (node) table in Cytoscape. For importing the data we will use the following mapping:
- Key Column for Network should be Entrez Gene, which is the column we just added.
- Gene should be the key of the data(
de.genes.full).
loadTableData(de.genes.full,data.key.column="Gene",table.key.column="Entrez Gene")
You will notice two new columns (logFC and FDR.adjusted.Pvalue) in the Node Table.
tail(getTableColumnNames('node'))
Visualization
Next, we will create a visualization of the imported data on the network.
setVisualStyle(style.name="default")
setNodeShapeDefault(new.shape="ELLIPSE", style.name = "default")
lockNodeDimensions(new.state="TRUE", style.name = "default")
setNodeSizeDefault(new.size="50", style.name = "default")
setNodeColorDefault(new.color="#D3D3D3", style.name = "default")
setNodeBorderWidthDefault(new.width="2", style.name = "default")
setNodeBorderColorDefault(new.color="#616060", style.name = "default")
setNodeLabelMapping(table.column="display name",style.name = "default")
setNodeFontSizeDefault(new.size="14", style.name = "default")
Before we create a mapping for node color representing the range of fold changes, we need the min and max of the logFC column:
logFC.table.up <- getTableColumns('node', 'logFC')
logFC.up.min <- min(logFC.table.up, na.rm = T)
logFC.up.max <- max(logFC.table.up, na.rm = T)
logFC.up.center <- logFC.up.min + (logFC.up.max - logFC.up.min)/2
copyVisualStyle(from.style = "default", to.style = "de genes up")
setVisualStyle(style.name="de genes up")
data.values = c(logFC.up.min, logFC.up.center, logFC.up.max)
node.colors <- c(brewer.pal(length(data.values), "YlOrRd"))
setNodeColorMapping('logFC', data.values, node.colors, style.name="de genes up")
Applying a force-directed layout, the network will now look something like this:
layoutNetwork(paste('force-directed',
'defaultSpringCoefficient=0.00003',
'defaultSpringLength=50',
'defaultNodeMass=4',
sep=' '))
Enrichment Analysis Options
Next, we are going to perform enrichment anlaysis uing the STRING app.
STRING Enrichment
The STRING app has built-in enrichment analysis functionality, which includes enrichment for GO Process, GO Component, GO Function, InterPro, KEGG Pathways, and PFAM.
First, we will run the enrichment on the whole network, against the genome:
string.cmd = 'string retrieve enrichment allNetSpecies="Homo sapiens", background=genome selectedNodesOnly="false"'
commandsRun(string.cmd)
string.cmd = 'string show enrichment'
commandsRun(string.cmd)
When the enrichment analysis is complete, a new tab titled STRING Enrichment will open in the Table Panel.
The STRING app includes several options for filtering and displaying the enrichment results. The features are all available at the top of the STRING Enrichment tab.
We are going to filter the table to only show GO Process:
string.cmd = 'string filter enrichment categories="GO Process", overlapCutoff = "0.5", removeOverlapping = "true"'
commandsRun(string.cmd)
Next, we will add a split donut chart to the nodes representing the top terms:
string.cmd = 'string show charts'
commandsRun(string.cmd)
STRING Protein Query: Down-regulated genes
We are going to repeat the network search, data integration, visualization and enrichment analysis for the set of down-regulated genes by using the first column of TCGA-Ovarian-MesenvsImmuno-data-down.csv:
de.genes.down <- read.table("https://cytoscape.github.io/cytoscape-tutorials/protocols/data/TCGA-Ovarian-MesenvsImmuno-data-down.csv", header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)
string.cmd = paste('string protein query query="', paste(de.genes.down$Gene, collapse = '\n'), '" cutoff=0.4 species="Homo sapiens"', sep = "")
commandsRun(string.cmd)
Subnetwork
Let’s select only the connected nodes to work with for the rest of this tutorial, by creating a subnetwork based on all edges:
createSubnetwork(edges='all', subnetwork.name='String de genes down')
Data integration
Again, the identifiers in the network needs to be mapped to Entrez Gene (NCBI gene):
mapped.cols <- mapTableColumn('display name', 'Human', 'HGNC', 'Entrez Gene')
We can now import the data:
loadTableData(de.genes.full,data.key.column="Gene",table.key.column="Entrez Gene")
Visualization
Next, we can create a visualization. Note that the default style has been altered in the previous example, so we can simply switch to default to get started:
setVisualStyle(style.name="default")
The node fill color has to be redefined for down-regulated genes:
logFC.table.down <- getTableColumns('node', 'logFC')
logFC.dn.min <- min(logFC.table.down, na.rm = T)
logFC.dn.max <- max(logFC.table.down, na.rm = T)
logFC.dn.center <- logFC.dn.min + (logFC.dn.max - logFC.dn.min)/2
copyVisualStyle(from.style = "default", to.style = "de genes down")
setVisualStyle(style.name="de genes down")
data.values = c(logFC.dn.min, logFC.dn.center, logFC.dn.max)
node.colors <- c(brewer.pal(length(data.values), "Blues"))
setNodeColorMapping('logFC', data.values, node.colors, style.name="de genes down")
Apply a force-directed layout.
layoutNetwork(paste('force-directed',
'defaultSpringCoefficient=0.00003',
'defaultSpringLength=50',
'defaultNodeMass=4',
sep=' '))
Focusing on the connected part of the network, it should look something like this:

STRING Enrichment
Now we can perform STRING Enrichment analysis on the resulting network:
string.cmd = 'string retrieve enrichment allNetSpecies="Homo sapiens", background=genome selectedNodesOnly="false"'
commandsRun(string.cmd)
string.cmd = 'string show enrichment'
commandsRun(string.cmd)
Filter the analysis results for non-redundant GO Process terms only.
string.cmd = 'string filter enrichment categories="GO Process", overlapCutoff = "0.5", removeOverlapping = "true"'
commandsRun(string.cmd)
string.cmd = 'string show charts'
commandsRun(string.cmd)
## This part doesn't work yet
##string.cmd = "string settings chartType='Pie Chart' nTerms=3 showEnhancedLabels='FALSE' showGlassBallEffect='FALSE' defaultPalette='ColorBrewer Set1 colors' showImage='FALSE'"
##commandsRun(string.cmd)
STRING Disease Query
So far, we queried the STRING database with a set of genes we knew were differentially expressed. Next, we will query the STRING disease database to retrieve a network genes associated with ovarian cancer, which will be completely independent of our dataset.
string.cmd = 'string disease query disease="ovarian cancer" cutoff="0.95"'
commandsRun(string.cmd)
This will bring in the top 100 (default) ovarian cancer associated genes connected with a confidence score greater than 0.95. Again, lets extract out the connected nodes:
createSubnetwork(edges='all', subnetwork.name='String ovarian sub')
Data integration
Next we will import differential gene expression data from our TCGA dataset to create a visualization. Just like the previous example, we will need to do some identifier mapping to match the data to the network.
mapped.cols <- mapTableColumn("display name",'Human','HGNC','Entrez Gene')
Here we set Human as species, HGNC as Map from, and Entrez Gene as To.
We can now import the data frame with the full data (already loaded the data in Example 1 above) into the node table in Cytoscape:
loadTableData(de.genes.full, data.key.column = "Gene", table = "node", table.key.column = "Entrez Gene")
Visualization
Again, we can create a visualization:
setVisualStyle(style.name="default")
Next, we need the min and max of the logFC column:
logFC.table.ovarian <- getTableColumns('node', 'logFC')
logFC.ov.min <- min(logFC.table.ovarian, na.rm = T)
logFC.ov.max <- max(logFC.table.ovarian, na.rm = T)
logFC.ov.center <- logFC.ov.min + (logFC.ov.max - logFC.ov.min)/2
Let’s create the mapping:
copyVisualStyle(from.style = "default", to.style = "ovarian")
setVisualStyle(style.name="ovarian")
data.values = c(logFC.ov.min, logFC.ov.center, logFC.ov.max)
node.colors <- c(brewer.pal(length(data.values), "RdBu"))
setNodeColorMapping('logFC', data.values, node.colors, style.name="ovarian")
Apply a force-directed layout.
layoutNetwork(paste('force-directed',
'defaultSpringCoefficient=0.00003',
'defaultSpringLength=50',
'defaultNodeMass=4',
sep=' '))
The TCGA found several genes that were commonly mutated in ovarian cancer, so called “cancer drivers”. We can add information about these genes to the network visualization, by changing the visual style of these nodes. Three of the most important drivers are TP53, BRCA1 and BRCA2. We will add a thicker, colored border for these genes in the network.
Select all three driver genes by:
selectNodes(c("TP53", "BRCA1", "BRCA2"), by.col = "display name")
Add a style bypass for node Border Width (5) and node Border Paint (bright pink):
setNodeBorderWidthBypass(getSelectedNodes(), 5)
setNodeBorderColorBypass(getSelectedNodes(), '#FF007F')
Exporting Networks
Cytoscape provides a number of ways to export results and visualizations:
exportImage('./differentially-expressed-genes', 'PDF')
exportImage('./differentially-expressed-genes', 'PNG')
exportImage('./differentially-expressed-genes', 'JPEG')
exportImage('./differentially-expressed-genes', 'SVG')
exportImage('./differentially-expressed-genes', 'PS')
exportNetworkToNDEx("user", "password", TRUE)
- As a Cytoscape JSON file:
exportNetwork('./differentially-expressed-genes', 'cyjs')
LS0tCnRpdGxlOiAiRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIEdlbmVzIE5ldHdvcmsgQW5hbHlzaXMiCmF1dGhvcjogIktvem8gTmlzaGlkYSwgS3Jpc3RpbmEgSGFuc3BlcnMgYW5kIEFsZXggUGljbyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiAibm9uZSIKLS0tCmBgYHtyLCBlY2hvID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBldmFsPUZBTFNFCikKYGBgCgoqVGhlIFIgbWFya2Rvd24gaXMgYXZhaWxhYmxlIGZyb20gdGhlIHB1bGxkb3duIG1lbnUgZm9yKiBDb2RlICphdCB0aGUgdXBwZXItcmlnaHQsIGNob29zZSAiRG93bmxvYWQgUm1kIiwgb3IgW2Rvd25sb2FkIHRoZSBSbWQgZnJvbSBHaXRIdWJdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9jeXRvc2NhcGUvY3l0b3NjYXBlLWF1dG9tYXRpb24vbWFzdGVyL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvRGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLWdlbmVzLlJtZCkuKgoKPGhyIC8+CgpUaGlzIHByb3RvY29sIGRlc2NyaWJlcyBhIG5ldHdvcmsgYW5hbHlzaXMgd29ya2Zsb3cgaW4gQ3l0b3NjYXBlIGZvciBhIHNldCBvZiBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMuIFBvaW50cyBjb3ZlcmVkOgoKLSBSZXRyaWV2aW5nIHJlbGV2YW50IG5ldHdvcmtzIGZyb20gcHVibGljIGRhdGFiYXNlcwotIE5ldHdvcmsgZnVuY3Rpb25hbCBlbnJpY2htZW50IGFuYWx5c2lzCi0gSW50ZWdyYXRpb24gYW5kIHZpc3VhbGl6YXRpb24gb2YgZXhwZXJpbWVudGFsIGRhdGEKLSBFeHBvcnRpbmcgbmV0d29yayB2aXN1YWxpemF0aW9ucwoKPGNlbnRlcj4KIVtdKC4vZGF0YS9pbWcvc3RyaW5nLW92YXJpYW4tZmluYWwucG5nKQo8L2NlbnRlcj4KCjxociAvPgoKIyBJbnN0YWxsYXRpb24KYGBge3IsIGV2YWwgPSBGQUxTRX0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQp9CgppZighIlJDeTMiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkpewogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSQ3kzIikKfQpsaWJyYXJ5KFJDeTMpCmBgYAoKIyBHZXR0aW5nIHN0YXJ0ZWQKRmlyc3QsIGxhdW5jaCBDeXRvc2NhcGUgYW5kIGtlZXAgaXQgcnVubmluZyB3aGVuZXZlciB1c2luZyBSQ3kzLiBDb25maXJtIHRoYXQgeW91IGhhdmUgZXZlcnl0aGluZyBpbnN0YWxsZWQgYW5kIHJ1bm5pbmc6CmBgYHtyfQpjeXRvc2NhcGVQaW5nKCkKY3l0b3NjYXBlVmVyc2lvbkluZm8oKQpgYGAKCiMgUHJlcmVxdWlzaXRlcwoKSWYgeW91IGhhdmVuJ3QgYWxyZWFkeSwgaW5zdGFsbCB0aGUgW1NUUklOR2FwcF0oaHR0cDovL2FwcHMuY3l0b3NjYXBlLm9yZy9hcHBzL3N0cmluZ2FwcCkKCmBgYHtyfQppbnN0YWxsQXBwKCdzdHJpbmdBcHAnKQpgYGAKCiMgQmFja2dyb3VuZAoKT3ZhcmlhbiBzZXJvdXMgY3lzdGFkZW5vY2FyY2lub21hIGlzIGEgdHlwZSBvZiBlcGl0aGVsaWFsIG92YXJpYW4gY2FuY2VyIHdoaWNoIGFjY291bnRzIGZvciB+OTAlIG9mIGFsbCBvdmFyaWFuIGNhbmNlcnMuClRoZSBkYXRhIHVzZWQgaW4gdGhpcyBwcm90b2NvbCBhcmUgZnJvbSBbVGhlIENhbmNlciBHZW5vbWUgQXRsYXNdKGh0dHBzOi8vY2FuY2VyZ2Vub21lLm5paC5nb3YvKSwgaW4gd2hpY2ggbXVsdGlwbGUgc3VidHlwZXMgb2Ygc2Vyb3VzIGN5c3RhZGVub2NhcmNpbm9tYSB3ZXJlIGlkZW50aWZpZWQgYW5kIGNoYXJhY3Rlcml6ZWQgYnkgbVJOQSBleHByZXNzaW9uLgoKV2Ugd2lsbCBmb2N1cyBvbiB0aGUgZGlmZmVyZW50aWFsIGdlbmUgZXhwcmVzc2lvbiBiZXR3ZWVuIHR3byBzdWJ0eXBlcywgKipNZXNlbmNoeW1hbCoqIGFuZCAqKkltbXVub3JlYWN0aXZlKiouCgpGb3IgY29udmVuaWVuY2UsIHRoZSBkYXRhIGhhcyBhbHJlYWR5IGJlZW4gYW5hbHl6ZWQgYW5kIHByZS1maWx0ZXJlZCwgdXNpbmcgbG9nIGZvbGQgY2hhbmdlIHZhbHVlIGFuZCBhZGp1c3RlZCBwLXZhbHVlLgoKIyBOZXR3b3JrIFJldHJpZXZhbAoKTWFueSBwdWJsaWMgZGF0YWJhc2VzIGFuZCBtdWx0aXBsZSBDeXRvc2NhcGUgYXBwcyBhbGxvdyB5b3UgdG8gcmV0cmlldmUgYSBuZXR3b3JrIG9yIHBhdGh3YXkgcmVsZXZhbnQgdG8geW91ciBkYXRhLgpGb3IgdGhpcyB3b3JrZmxvdywgd2Ugd2lsbCB1c2UgdGhlIFNUUklORyBhcHAuIFNvbWUgb3RoZXIgb3B0aW9ucyBpbmNsdWRlOgoKLSBbV2lraVBhdGh3YXlzXShodHRwczovL25ybmIub3JnL2dzb2QyMDE5X2tvem9fbmlzaGlkYS9odG1sX2RvY3VtZW50cy9SbWQvd2lraXBhdGh3YXlzLWFwcC5odG1sKQotIFtOREV4XShodHRwOi8vd3d3Lm5kZXhiaW8ub3JnLykKLSBbR2VuZU1BTklBXShodHRwczovL2dlbmVtYW5pYS5vcmcvKQoKIyBSZXRyaWV2ZSBOZXR3b3JrcyBmcm9tIFNUUklORwoKVG8gaWRlbnRpZnkgYSByZWxldmFudCBuZXR3b3JrLCB3ZSB3aWxsIHF1ZXJ5IHRoZSBTVFJJTkcgZGF0YWJhc2UgaW4gdHdvIGRpZmZlcmVudCB3YXlzOgoKLSBRdWVyeSAqKlNUUklORyBwcm90ZWluKiogd2l0aCB0aGUgbGlzdCBvZiBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMuCi0gUXVlcnkgKipTVFJJTkcgZGlzZWFzZSoqIGZvciBhIGtleXdvcmQ7ICoqb3ZhcmlhbiBjYW5jZXIqKi4KClRoZSB0d28gZXhhbXBsZXMgYXJlIHNwbGl0IGludG8gdHdvIHNlcGFyYXRlIHdvcmtmbG93cyBiZWxvdy4KCiMgRXhhbXBsZSAxOiBTVFJJTkcgUHJvdGVpbiBRdWVyeSBVcC1yZWd1bGF0ZWQgR2VuZXMKCkxvYWQgdGhlIGZpbGUgY29udGFpbmluZyB0aGUgZGlmZmVyZW50aWFsIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGFuZCBmaWx0ZXIgZm9yIHVwLXJlZ3VsYXRlZCBnZW5lcywgVENHQS1PdmFyaWFuLU1lc2VudnNJbW11bm9fZGF0YS5jc3Y6CgpgYGB7cn0KZGUuZ2VuZXMgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtdHV0b3JpYWxzL2doLXBhZ2VzL3Byb3RvY29scy9kYXRhL1RDR0EtT3Zhcmlhbi1NZXNlbnZzSW1tdW5vX2RhdGEuY3N2IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIiwiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmRlLmdlbmVzLnVwIDwtIGRlLmdlbmVzW3doaWNoKGRlLmdlbmVzJGxvZ0ZDID49IDEuOCAmIGRlLmdlbmVzJEZEUi5hZGp1c3RlZC5QdmFsdWUgPD0gMC4wNSksXQpgYGAKCldlIHdpbGwgdXNlIHRoZSBpZGVudGlmaWVycyBpbiB0aGUgZmlyc3QgY29sdW1uIG9mIHRoaXMgZGF0YWZpbGUgdG8gcnVuIGEgKipTVFJJTkcgcHJvdGVpbiBxdWVyeSoqLCB3aXRoIGNvbmZpZGVuY2UgKHNjb3JlKSBjdXRvZmYgb2YgMC40OgpgYGB7cn0Kc3RyaW5nLmNtZCA9IHBhc3RlKCdzdHJpbmcgcHJvdGVpbiBxdWVyeSBxdWVyeT0iJywgcGFzdGUoZGUuZ2VuZXMudXAkR2VuZSwgY29sbGFwc2UgPSAnXG4nKSwgJyIgY3V0b2ZmPTAuNCAgc3BlY2llcz0iSG9tbyBzYXBpZW5zIicsIHNlcCA9ICIiKQpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKClRoZSByZXN1bHRpbmcgbmV0d29yayB3aWxsIGxvYWQgYXV0b21hdGljYWxseSBhbmQgY29udGFpbnMgdXAtcmVndWxhdGVkIGdlbmVzIHJlY29nbml6ZWQgYnkgU1RSSU5HLCBhbmQgaW50ZXJhY3Rpb25zIGJldHdlZW4gdGhlbSB3aXRoIGFuIGV2aWRlbmNlIHNjb3JlIG9mIDAuNCBvciBncmVhdGVyLgoKPGNlbnRlcj4KIVtdKC4vZGF0YS9pbWcvc3RyaW5nLWRlLXVwMi5wbmcpCjwvY2VudGVyPgoKVGhlIG5ldHdvcmtzIGNvbnNpc3RzIG9mIG9uZSBsYXJnZSBjb25uZWN0ZWQgY29tcG9uZW50LCBzZXZlcmFsIHNtYWxsZXIgbmV0d29ya3MsIGFuZCBzb21lIHVuY29ubmVjdGVkIG5vZGVzLiBXZSB3aWxsIHNlbGVjdCBvbmx5IHRoZSBjb25uZWN0ZWQgbm9kZXMgdG8gd29yayB3aXRoIGZvciB0aGUgcmVzdCBvZiB0aGlzIHR1dG9yaWFsLCBieSBjcmVhdGluZyBhIHN1Ym5ldHdvcmsgYmFzZWQgb24gYWxsIGVkZ2VzOgoKYGBge3J9CmNyZWF0ZVN1Ym5ldHdvcmsoZWRnZXM9J2FsbCcsIHN1Ym5ldHdvcmsubmFtZT0nU3RyaW5nIGRlIGdlbmVzIHVwJykKYGBgCgojIERhdGEgSW50ZWdyYXRpb24KCk5leHQgd2Ugd2lsbCBjcmVhdGUgYSB2aXN1YWxpemF0aW9uIGZyb20gdGhlIGxvZyBmb2xkIGNoYW5nZXMgYW5kIHAtdmFsdWVzIGZyb20gb3VyIFRDR0EgZGF0YXNldC4gU2luY2UgdGhlIFNUUklORyBuZXR3b3JrIGlzIGEgcHJvdGVpbi1wcm90ZWluIG5ldHdvcmssIGl0IGlzIGFubm90YXRlZCB3aXRoIHByb3RlaW4gaWRlbnRpZmllcnMgKFVuaXByb3QgYW5kIEVuc2VtYmwgcHJvdGVpbiksIGFzIHdlbGwgYXMgSEdOQyBnZW5lIHN5bWJvbHMuIE91ciBkYXRhIGZyb20gVENHQSBoYXMgTkNCSSBHZW5lIGlkZW50aWZpZXJzIChmb3JtZXJseSBFbnRyZXopLCBzbyBiZWZvcmUgaW1wb3J0aW5nIHRoZSBkYXRhIHdlIGFyZSBnb2luZyB0byB1c2UgdGhlIElEIE1hcHBlciBmdW5jdGlvbmFsaXR5IGluIEN5dG9zY2FwZSB0byBtYXAgdGhlIG5ldHdvcmsgdG8gTkNCSSBHZW5lLgoKYGBge3J9Cm1hcHBlZC5jb2xzIDwtIG1hcFRhYmxlQ29sdW1uKCdkaXNwbGF5IG5hbWUnLCAnSHVtYW4nLCAnSEdOQycsICdFbnRyZXogR2VuZScpCmBgYAoKV2UgY2FuIG5vdyBpbnRlZ3JhdGUgdGhlIHNhbWUgZGlmZmVyZW50aWFsIGdlbmUgZXhwcmVzc2lvbiBkYXRhIHdpdGggdGhlIG5ldHdvcmsgKG5vZGUpIHRhYmxlIGluIEN5dG9zY2FwZS4KRm9yIGltcG9ydGluZyB0aGUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZm9sbG93aW5nIG1hcHBpbmc6IAoKLSAqKktleSBDb2x1bW4gZm9yIE5ldHdvcmsqKiBzaG91bGQgYmUgKipFbnRyZXogR2VuZSoqLCB3aGljaCBpcyB0aGUgY29sdW1uIHdlIGp1c3QgYWRkZWQuCi0gKipHZW5lKiogc2hvdWxkIGJlIHRoZSBrZXkgb2YgdGhlIGRhdGEoYGRlLmdlbmVzLmZ1bGxgKS4KCmBgYHtyfQpsb2FkVGFibGVEYXRhKGRlLmdlbmVzLmZ1bGwsZGF0YS5rZXkuY29sdW1uPSJHZW5lIix0YWJsZS5rZXkuY29sdW1uPSJFbnRyZXogR2VuZSIpCmBgYAoKWW91IHdpbGwgbm90aWNlIHR3byBuZXcgY29sdW1ucyAobG9nRkMgYW5kIEZEUi5hZGp1c3RlZC5QdmFsdWUpIGluIHRoZSBOb2RlIFRhYmxlLiAKCmBgYHtyfQp0YWlsKGdldFRhYmxlQ29sdW1uTmFtZXMoJ25vZGUnKSkKYGBgCgojIFZpc3VhbGl6YXRpb24KTmV4dCwgd2Ugd2lsbCBjcmVhdGUgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBpbXBvcnRlZCBkYXRhIG9uIHRoZSBuZXR3b3JrLiAKCmBgYHtyfQpzZXRWaXN1YWxTdHlsZShzdHlsZS5uYW1lPSJkZWZhdWx0IikKc2V0Tm9kZVNoYXBlRGVmYXVsdChuZXcuc2hhcGU9IkVMTElQU0UiLCBzdHlsZS5uYW1lID0gImRlZmF1bHQiKQpsb2NrTm9kZURpbWVuc2lvbnMobmV3LnN0YXRlPSJUUlVFIiwgc3R5bGUubmFtZSA9ICJkZWZhdWx0IikKc2V0Tm9kZVNpemVEZWZhdWx0KG5ldy5zaXplPSI1MCIsIHN0eWxlLm5hbWUgPSAiZGVmYXVsdCIpCnNldE5vZGVDb2xvckRlZmF1bHQobmV3LmNvbG9yPSIjRDNEM0QzIiwgc3R5bGUubmFtZSA9ICJkZWZhdWx0IikKc2V0Tm9kZUJvcmRlcldpZHRoRGVmYXVsdChuZXcud2lkdGg9IjIiLCBzdHlsZS5uYW1lID0gImRlZmF1bHQiKQpzZXROb2RlQm9yZGVyQ29sb3JEZWZhdWx0KG5ldy5jb2xvcj0iIzYxNjA2MCIsIHN0eWxlLm5hbWUgPSAiZGVmYXVsdCIpCnNldE5vZGVMYWJlbE1hcHBpbmcodGFibGUuY29sdW1uPSJkaXNwbGF5IG5hbWUiLHN0eWxlLm5hbWUgPSAiZGVmYXVsdCIpCnNldE5vZGVGb250U2l6ZURlZmF1bHQobmV3LnNpemU9IjE0Iiwgc3R5bGUubmFtZSA9ICJkZWZhdWx0IikKYGBgCgpCZWZvcmUgd2UgY3JlYXRlIGEgbWFwcGluZyBmb3Igbm9kZSBjb2xvciByZXByZXNlbnRpbmcgdGhlIHJhbmdlIG9mIGZvbGQgY2hhbmdlcywgd2UgbmVlZCB0aGUgbWluIGFuZCBtYXggb2YgdGhlICoqbG9nRkMqKiBjb2x1bW46CgpgYGB7cn0KbG9nRkMudGFibGUudXAgPC0gZ2V0VGFibGVDb2x1bW5zKCdub2RlJywgJ2xvZ0ZDJykKYGBgCgpgYGB7cn0KbG9nRkMudXAubWluIDwtIG1pbihsb2dGQy50YWJsZS51cCwgbmEucm0gPSBUKQpsb2dGQy51cC5tYXggPC0gbWF4KGxvZ0ZDLnRhYmxlLnVwLCBuYS5ybSA9IFQpCmxvZ0ZDLnVwLmNlbnRlciA8LSBsb2dGQy51cC5taW4gKyAobG9nRkMudXAubWF4IC0gbG9nRkMudXAubWluKS8yCmBgYAoKYGBge3J9CmNvcHlWaXN1YWxTdHlsZShmcm9tLnN0eWxlID0gImRlZmF1bHQiLCB0by5zdHlsZSA9ICJkZSBnZW5lcyB1cCIpCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWU9ImRlIGdlbmVzIHVwIikKCmRhdGEudmFsdWVzID0gYyhsb2dGQy51cC5taW4sIGxvZ0ZDLnVwLmNlbnRlciwgbG9nRkMudXAubWF4KQpub2RlLmNvbG9ycyA8LSBjKGJyZXdlci5wYWwobGVuZ3RoKGRhdGEudmFsdWVzKSwgIllsT3JSZCIpKQpzZXROb2RlQ29sb3JNYXBwaW5nKCdsb2dGQycsIGRhdGEudmFsdWVzLCBub2RlLmNvbG9ycywgc3R5bGUubmFtZT0iZGUgZ2VuZXMgdXAiKQpgYGAKCkFwcGx5aW5nIGEgZm9yY2UtZGlyZWN0ZWQgbGF5b3V0LCB0aGUgbmV0d29yayB3aWxsIG5vdyBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6CgpgYGB7cn0KbGF5b3V0TmV0d29yayhwYXN0ZSgnZm9yY2UtZGlyZWN0ZWQnLCAKICAgICAgICAgICAgICAnZGVmYXVsdFNwcmluZ0NvZWZmaWNpZW50PTAuMDAwMDMnLAogICAgICAgICAgICAgICdkZWZhdWx0U3ByaW5nTGVuZ3RoPTUwJywKICAgICAgICAgICAgICAnZGVmYXVsdE5vZGVNYXNzPTQnLAogICAgICAgICAgICAgIHNlcD0nICcpKQpgYGAKCjxjZW50ZXI+CiFbXSguL2RhdGEvaW1nL3N0cmluZy1kZS11cC1mb3JjZWRpcmVjdGVkLnBuZykKPC9jZW50ZXI+CgojIEVucmljaG1lbnQgQW5hbHlzaXMgT3B0aW9ucwoKTmV4dCwgd2UgYXJlIGdvaW5nIHRvIHBlcmZvcm0gZW5yaWNobWVudCBhbmxheXNpcyB1aW5nIHRoZSBTVFJJTkcgYXBwLgoKIyMgU1RSSU5HIEVucmljaG1lbnQKClRoZSBTVFJJTkcgYXBwIGhhcyBidWlsdC1pbiBlbnJpY2htZW50IGFuYWx5c2lzIGZ1bmN0aW9uYWxpdHksIHdoaWNoIGluY2x1ZGVzIGVucmljaG1lbnQgZm9yIEdPIFByb2Nlc3MsIEdPIENvbXBvbmVudCwgR08gRnVuY3Rpb24sIEludGVyUHJvLCBLRUdHIFBhdGh3YXlzLCBhbmQgUEZBTS4KCkZpcnN0LCB3ZSB3aWxsIHJ1biB0aGUgZW5yaWNobWVudCBvbiB0aGUgd2hvbGUgbmV0d29yaywgYWdhaW5zdCB0aGUgZ2Vub21lOiAKCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyByZXRyaWV2ZSBlbnJpY2htZW50IGFsbE5ldFNwZWNpZXM9IkhvbW8gc2FwaWVucyIsIGJhY2tncm91bmQ9Z2Vub21lICBzZWxlY3RlZE5vZGVzT25seT0iZmFsc2UiJwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpzdHJpbmcuY21kID0gJ3N0cmluZyBzaG93IGVucmljaG1lbnQnCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKV2hlbiB0aGUgZW5yaWNobWVudCBhbmFseXNpcyBpcyBjb21wbGV0ZSwgYSBuZXcgdGFiIHRpdGxlZCAqKlNUUklORyBFbnJpY2htZW50Kiogd2lsbCBvcGVuIGluIHRoZSAqKlRhYmxlIFBhbmVsKiouCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUub3JnL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nLWVucmljaG1lbnQucG5nKQo8L2NlbnRlcj4KClRoZSBTVFJJTkcgYXBwIGluY2x1ZGVzIHNldmVyYWwgb3B0aW9ucyBmb3IgZmlsdGVyaW5nIGFuZCBkaXNwbGF5aW5nIHRoZSBlbnJpY2htZW50IHJlc3VsdHMuClRoZSBmZWF0dXJlcyBhcmUgYWxsIGF2YWlsYWJsZSBhdCB0aGUgdG9wIG9mIHRoZSAqKlNUUklORyBFbnJpY2htZW50KiogdGFiLgoKV2UgYXJlIGdvaW5nIHRvIGZpbHRlciB0aGUgdGFibGUgdG8gb25seSBzaG93IEdPIFByb2Nlc3M6CgpgYGB7cn0Kc3RyaW5nLmNtZCA9ICdzdHJpbmcgZmlsdGVyIGVucmljaG1lbnQgY2F0ZWdvcmllcz0iR08gUHJvY2VzcyIsIG92ZXJsYXBDdXRvZmYgPSAiMC41IiwgcmVtb3ZlT3ZlcmxhcHBpbmcgPSAidHJ1ZSInCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKTmV4dCwgd2Ugd2lsbCBhZGQgYSBzcGxpdCBkb251dCBjaGFydCB0byB0aGUgbm9kZXMgcmVwcmVzZW50aW5nIHRoZSB0b3AgdGVybXM6CgpgYGB7cn0Kc3RyaW5nLmNtZCA9ICdzdHJpbmcgc2hvdyBjaGFydHMnCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKIyMgU1RSSU5HIFByb3RlaW4gUXVlcnk6IERvd24tcmVndWxhdGVkIGdlbmVzCgpXZSBhcmUgZ29pbmcgdG8gcmVwZWF0IHRoZSBuZXR3b3JrIHNlYXJjaCwgZGF0YSBpbnRlZ3JhdGlvbiwgdmlzdWFsaXphdGlvbiBhbmQgZW5yaWNobWVudCBhbmFseXNpcyBmb3IgdGhlIHNldCBvZiBkb3duLXJlZ3VsYXRlZCBnZW5lcyBieSB1c2luZyB0aGUgZmlyc3QgY29sdW1uIG9mIFtUQ0dBLU92YXJpYW4tTWVzZW52c0ltbXVuby1kYXRhLWRvd24uY3N2XShodHRwczovL2N5dG9zY2FwZS5naXRodWIuaW8vY3l0b3NjYXBlLXR1dG9yaWFscy9wcm90b2NvbHMvZGF0YS9UQ0dBLU92YXJpYW4tTWVzZW52c0ltbXVuby1kYXRhLWRvd24uY3N2KToKCmBgYHtyfQpkZS5nZW5lcy5kb3duIDwtIHJlYWQudGFibGUoImh0dHBzOi8vY3l0b3NjYXBlLmdpdGh1Yi5pby9jeXRvc2NhcGUtdHV0b3JpYWxzL3Byb3RvY29scy9kYXRhL1RDR0EtT3Zhcmlhbi1NZXNlbnZzSW1tdW5vLWRhdGEtZG93bi5jc3YiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnN0cmluZy5jbWQgPSBwYXN0ZSgnc3RyaW5nIHByb3RlaW4gcXVlcnkgcXVlcnk9IicsIHBhc3RlKGRlLmdlbmVzLmRvd24kR2VuZSwgY29sbGFwc2UgPSAnXG4nKSwgJyIgY3V0b2ZmPTAuNCAgc3BlY2llcz0iSG9tbyBzYXBpZW5zIicsIHNlcCA9ICIiKQpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKCiMjIFN1Ym5ldHdvcmsKCkxldCdzIHNlbGVjdCBvbmx5IHRoZSBjb25uZWN0ZWQgbm9kZXMgdG8gd29yayB3aXRoIGZvciB0aGUgcmVzdCBvZiB0aGlzIHR1dG9yaWFsLCBieSBjcmVhdGluZyBhIHN1Ym5ldHdvcmsgYmFzZWQgb24gYWxsIGVkZ2VzOgoKYGBge3J9CmNyZWF0ZVN1Ym5ldHdvcmsoZWRnZXM9J2FsbCcsIHN1Ym5ldHdvcmsubmFtZT0nU3RyaW5nIGRlIGdlbmVzIGRvd24nKQpgYGAKCiMjIERhdGEgaW50ZWdyYXRpb24KCkFnYWluLCB0aGUgaWRlbnRpZmllcnMgaW4gdGhlIG5ldHdvcmsgbmVlZHMgdG8gYmUgbWFwcGVkIHRvIEVudHJleiBHZW5lIChOQ0JJIGdlbmUpOgoKYGBge3J9Cm1hcHBlZC5jb2xzIDwtIG1hcFRhYmxlQ29sdW1uKCdkaXNwbGF5IG5hbWUnLCAnSHVtYW4nLCAnSEdOQycsICdFbnRyZXogR2VuZScpCmBgYAoKV2UgY2FuIG5vdyBpbXBvcnQgdGhlIGRhdGE6CgpgYGB7cn0KbG9hZFRhYmxlRGF0YShkZS5nZW5lcy5mdWxsLGRhdGEua2V5LmNvbHVtbj0iR2VuZSIsdGFibGUua2V5LmNvbHVtbj0iRW50cmV6IEdlbmUiKQpgYGAKCiMjIFZpc3VhbGl6YXRpb24KTmV4dCwgd2UgY2FuIGNyZWF0ZSBhIHZpc3VhbGl6YXRpb24uIE5vdGUgdGhhdCB0aGUgYGRlZmF1bHRgIHN0eWxlIGhhcyBiZWVuIGFsdGVyZWQgaW4gdGhlIHByZXZpb3VzIGV4YW1wbGUsIHNvIHdlIGNhbiBzaW1wbHkgc3dpdGNoIHRvIGBkZWZhdWx0YCB0byBnZXQgc3RhcnRlZDoKCmBgYHtyfQpzZXRWaXN1YWxTdHlsZShzdHlsZS5uYW1lPSJkZWZhdWx0IikKYGBgCgpUaGUgbm9kZSBmaWxsIGNvbG9yIGhhcyB0byBiZSByZWRlZmluZWQgZm9yIGRvd24tcmVndWxhdGVkIGdlbmVzOgpgYGB7cn0KbG9nRkMudGFibGUuZG93biA8LSBnZXRUYWJsZUNvbHVtbnMoJ25vZGUnLCAnbG9nRkMnKQpgYGAKCmBgYHtyfQpsb2dGQy5kbi5taW4gPC0gbWluKGxvZ0ZDLnRhYmxlLmRvd24sIG5hLnJtID0gVCkKbG9nRkMuZG4ubWF4IDwtIG1heChsb2dGQy50YWJsZS5kb3duLCBuYS5ybSA9IFQpCmxvZ0ZDLmRuLmNlbnRlciA8LSBsb2dGQy5kbi5taW4gKyAobG9nRkMuZG4ubWF4IC0gbG9nRkMuZG4ubWluKS8yCmBgYAoKYGBge3J9CmNvcHlWaXN1YWxTdHlsZShmcm9tLnN0eWxlID0gImRlZmF1bHQiLCB0by5zdHlsZSA9ICJkZSBnZW5lcyBkb3duIikKc2V0VmlzdWFsU3R5bGUoc3R5bGUubmFtZT0iZGUgZ2VuZXMgZG93biIpCgpkYXRhLnZhbHVlcyA9IGMobG9nRkMuZG4ubWluLCBsb2dGQy5kbi5jZW50ZXIsIGxvZ0ZDLmRuLm1heCkKbm9kZS5jb2xvcnMgPC0gYyhicmV3ZXIucGFsKGxlbmd0aChkYXRhLnZhbHVlcyksICJCbHVlcyIpKQpzZXROb2RlQ29sb3JNYXBwaW5nKCdsb2dGQycsIGRhdGEudmFsdWVzLCBub2RlLmNvbG9ycywgc3R5bGUubmFtZT0iZGUgZ2VuZXMgZG93biIpCgpgYGAKCkFwcGx5IGEgZm9yY2UtZGlyZWN0ZWQgbGF5b3V0LgoKYGBge3J9CmxheW91dE5ldHdvcmsocGFzdGUoJ2ZvcmNlLWRpcmVjdGVkJywgCiAgICAgICAgICAgICAgJ2RlZmF1bHRTcHJpbmdDb2VmZmljaWVudD0wLjAwMDAzJywKICAgICAgICAgICAgICAnZGVmYXVsdFNwcmluZ0xlbmd0aD01MCcsCiAgICAgICAgICAgICAgJ2RlZmF1bHROb2RlTWFzcz00JywKICAgICAgICAgICAgICBzZXA9JyAnKSkKYGBgCgpGb2N1c2luZyBvbiB0aGUgY29ubmVjdGVkIHBhcnQgb2YgdGhlIG5ldHdvcmssIGl0IHNob3VsZCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6CjxjZW50ZXI+CiFbXSguL2RhdGEvaW1nL3N0cmluZy1kZS1kb3duLWZvcmNlZGlyZWN0ZWQucG5nKQoKIyMgU1RSSU5HIEVucmljaG1lbnQKCk5vdyB3ZSBjYW4gcGVyZm9ybSBTVFJJTkcgRW5yaWNobWVudCBhbmFseXNpcyBvbiB0aGUgcmVzdWx0aW5nIG5ldHdvcms6CgpgYGB7cn0Kc3RyaW5nLmNtZCA9ICdzdHJpbmcgcmV0cmlldmUgZW5yaWNobWVudCBhbGxOZXRTcGVjaWVzPSJIb21vIHNhcGllbnMiLCBiYWNrZ3JvdW5kPWdlbm9tZSAgc2VsZWN0ZWROb2Rlc09ubHk9ImZhbHNlIicKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKc3RyaW5nLmNtZCA9ICdzdHJpbmcgc2hvdyBlbnJpY2htZW50Jwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKCkZpbHRlciB0aGUgYW5hbHlzaXMgcmVzdWx0cyBmb3Igbm9uLXJlZHVuZGFudCBHTyBQcm9jZXNzIHRlcm1zIG9ubHkuIAoKYGBge3J9CnN0cmluZy5jbWQgPSAnc3RyaW5nIGZpbHRlciBlbnJpY2htZW50IGNhdGVnb3JpZXM9IkdPIFByb2Nlc3MiLCBvdmVybGFwQ3V0b2ZmID0gIjAuNSIsIHJlbW92ZU92ZXJsYXBwaW5nID0gInRydWUiJwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyBzaG93IGNoYXJ0cycKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKCiMjIFRoaXMgcGFydCBkb2Vzbid0IHdvcmsgeWV0CiMjc3RyaW5nLmNtZCA9ICJzdHJpbmcgc2V0dGluZ3MgY2hhcnRUeXBlPSdQaWUgQ2hhcnQnIG5UZXJtcz0zIHNob3dFbmhhbmNlZExhYmVscz0nRkFMU0UnIHNob3dHbGFzc0JhbGxFZmZlY3Q9J0ZBTFNFJyBkZWZhdWx0UGFsZXR0ZT0nQ29sb3JCcmV3ZXIgU2V0MSBjb2xvcnMnIHNob3dJbWFnZT0nRkFMU0UnIgojI2NvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKIyMgU1RSSU5HIERpc2Vhc2UgUXVlcnkKClNvIGZhciwgd2UgcXVlcmllZCB0aGUgU1RSSU5HIGRhdGFiYXNlIHdpdGggYSBzZXQgb2YgZ2VuZXMgd2Uga25ldyB3ZXJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZC4gTmV4dCwgd2Ugd2lsbCBxdWVyeSB0aGUgKipTVFJJTkcgZGlzZWFzZSoqIGRhdGFiYXNlIHRvIHJldHJpZXZlIGEgbmV0d29yayBnZW5lcyBhc3NvY2lhdGVkIHdpdGggb3ZhcmlhbiBjYW5jZXIsIHdoaWNoIHdpbGwgYmUgY29tcGxldGVseSBpbmRlcGVuZGVudCBvZiBvdXIgZGF0YXNldC4KCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyBkaXNlYXNlIHF1ZXJ5IGRpc2Vhc2U9Im92YXJpYW4gY2FuY2VyIiBjdXRvZmY9IjAuOTUiJwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKClRoaXMgd2lsbCBicmluZyBpbiB0aGUgdG9wIDEwMCAoZGVmYXVsdCkgb3ZhcmlhbiBjYW5jZXIgYXNzb2NpYXRlZCBnZW5lcyBjb25uZWN0ZWQgd2l0aCBhIGNvbmZpZGVuY2Ugc2NvcmUgZ3JlYXRlciB0aGFuIDAuOTUuIEFnYWluLCBsZXRzIGV4dHJhY3Qgb3V0IHRoZSBjb25uZWN0ZWQgbm9kZXM6CgpgYGB7cn0KY3JlYXRlU3VibmV0d29yayhlZGdlcz0nYWxsJywgc3VibmV0d29yay5uYW1lPSdTdHJpbmcgb3ZhcmlhbiBzdWInKQpgYGAKCiMgRGF0YSBpbnRlZ3JhdGlvbgoKTmV4dCB3ZSB3aWxsIGltcG9ydCBkaWZmZXJlbnRpYWwgZ2VuZSBleHByZXNzaW9uIGRhdGEgZnJvbSBvdXIgVENHQSBkYXRhc2V0IHRvIGNyZWF0ZSBhIHZpc3VhbGl6YXRpb24uIEp1c3QgbGlrZSB0aGUgcHJldmlvdXMgZXhhbXBsZSwgd2Ugd2lsbCBuZWVkIHRvIGRvIHNvbWUgaWRlbnRpZmllciBtYXBwaW5nIHRvIG1hdGNoIHRoZSBkYXRhIHRvIHRoZSBuZXR3b3JrLgoKYGBge3J9Cm1hcHBlZC5jb2xzIDwtIG1hcFRhYmxlQ29sdW1uKCJkaXNwbGF5IG5hbWUiLCdIdW1hbicsJ0hHTkMnLCdFbnRyZXogR2VuZScpCmBgYAoKSGVyZSB3ZSBzZXQgKipIdW1hbioqIGFzIHNwZWNpZXMsICoqSEdOQyoqIGFzICoqTWFwIGZyb20qKiwgYW5kICoqRW50cmV6IEdlbmUqKiBhcyAqKlRvKiouCgpXZSBjYW4gbm93IGltcG9ydCB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBmdWxsIGRhdGEgKGFscmVhZHkgbG9hZGVkIHRoZSBkYXRhIGluIEV4YW1wbGUgMSBhYm92ZSkgaW50byB0aGUgbm9kZSB0YWJsZSBpbiBDeXRvc2NhcGU6CgpgYGB7cn0KbG9hZFRhYmxlRGF0YShkZS5nZW5lcy5mdWxsLCBkYXRhLmtleS5jb2x1bW4gPSAiR2VuZSIsIHRhYmxlID0gIm5vZGUiLCB0YWJsZS5rZXkuY29sdW1uID0gIkVudHJleiBHZW5lIikKYGBgCgojIFZpc3VhbGl6YXRpb24KCkFnYWluLCB3ZSBjYW4gY3JlYXRlIGEgdmlzdWFsaXphdGlvbjoKCmBgYHtyfQpzZXRWaXN1YWxTdHlsZShzdHlsZS5uYW1lPSJkZWZhdWx0IikKYGBgCgpOZXh0LCB3ZSBuZWVkIHRoZSBtaW4gYW5kIG1heCBvZiB0aGUgKipsb2dGQyoqIGNvbHVtbjoKCmBgYHtyfQpsb2dGQy50YWJsZS5vdmFyaWFuIDwtIGdldFRhYmxlQ29sdW1ucygnbm9kZScsICdsb2dGQycpCmBgYAoKYGBge3J9CmxvZ0ZDLm92Lm1pbiA8LSBtaW4obG9nRkMudGFibGUub3ZhcmlhbiwgbmEucm0gPSBUKQpsb2dGQy5vdi5tYXggPC0gbWF4KGxvZ0ZDLnRhYmxlLm92YXJpYW4sIG5hLnJtID0gVCkKbG9nRkMub3YuY2VudGVyIDwtIGxvZ0ZDLm92Lm1pbiArIChsb2dGQy5vdi5tYXggLSBsb2dGQy5vdi5taW4pLzIKYGBgCgpMZXQncyBjcmVhdGUgdGhlIG1hcHBpbmc6IAoKYGBge3J9CmNvcHlWaXN1YWxTdHlsZShmcm9tLnN0eWxlID0gImRlZmF1bHQiLCB0by5zdHlsZSA9ICJvdmFyaWFuIikKc2V0VmlzdWFsU3R5bGUoc3R5bGUubmFtZT0ib3ZhcmlhbiIpCgpkYXRhLnZhbHVlcyA9IGMobG9nRkMub3YubWluLCBsb2dGQy5vdi5jZW50ZXIsIGxvZ0ZDLm92Lm1heCkKbm9kZS5jb2xvcnMgPC0gYyhicmV3ZXIucGFsKGxlbmd0aChkYXRhLnZhbHVlcyksICJSZEJ1IikpCnNldE5vZGVDb2xvck1hcHBpbmcoJ2xvZ0ZDJywgZGF0YS52YWx1ZXMsIG5vZGUuY29sb3JzLCBzdHlsZS5uYW1lPSJvdmFyaWFuIikKYGBgCkFwcGx5IGEgZm9yY2UtZGlyZWN0ZWQgbGF5b3V0LgoKYGBge3J9CmxheW91dE5ldHdvcmsocGFzdGUoJ2ZvcmNlLWRpcmVjdGVkJywgCiAgICAgICAgICAgICAgJ2RlZmF1bHRTcHJpbmdDb2VmZmljaWVudD0wLjAwMDAzJywKICAgICAgICAgICAgICAnZGVmYXVsdFNwcmluZ0xlbmd0aD01MCcsCiAgICAgICAgICAgICAgJ2RlZmF1bHROb2RlTWFzcz00JywKICAgICAgICAgICAgICBzZXA9JyAnKSkKYGBgIAoKVGhlIFRDR0EgZm91bmQgc2V2ZXJhbCBnZW5lcyB0aGF0IHdlcmUgY29tbW9ubHkgbXV0YXRlZCBpbiBvdmFyaWFuIGNhbmNlciwgc28gY2FsbGVkICJjYW5jZXIgZHJpdmVycyIuCldlIGNhbiBhZGQgaW5mb3JtYXRpb24gYWJvdXQgdGhlc2UgZ2VuZXMgdG8gdGhlIG5ldHdvcmsgdmlzdWFsaXphdGlvbiwgYnkgY2hhbmdpbmcgdGhlIHZpc3VhbCBzdHlsZSBvZiB0aGVzZSBub2Rlcy4KVGhyZWUgb2YgdGhlIG1vc3QgaW1wb3J0YW50IGRyaXZlcnMgYXJlICoqVFA1MyoqLCAqKkJSQ0ExKiogYW5kICoqQlJDQTIqKi4KV2Ugd2lsbCBhZGQgYSB0aGlja2VyLCBjb2xvcmVkIGJvcmRlciBmb3IgdGhlc2UgZ2VuZXMgaW4gdGhlIG5ldHdvcmsuCgpTZWxlY3QgYWxsIHRocmVlIGRyaXZlciBnZW5lcyBieToKCmBgYHtyfQpzZWxlY3ROb2RlcyhjKCJUUDUzIiwgIkJSQ0ExIiwgIkJSQ0EyIiksIGJ5LmNvbCA9ICJkaXNwbGF5IG5hbWUiKQpgYGAKCkFkZCBhIHN0eWxlIGJ5cGFzcyBmb3Igbm9kZSAqKkJvcmRlciBXaWR0aCoqICg1KSBhbmQgbm9kZSAqKkJvcmRlciBQYWludCoqIChicmlnaHQgcGluayk6CgpgYGB7cn0Kc2V0Tm9kZUJvcmRlcldpZHRoQnlwYXNzKGdldFNlbGVjdGVkTm9kZXMoKSwgNSkKc2V0Tm9kZUJvcmRlckNvbG9yQnlwYXNzKGdldFNlbGVjdGVkTm9kZXMoKSwgJyNGRjAwN0YnKQpgYGAKCjxjZW50ZXI+CiFbXSguL2RhdGEvaW1nL3N0cmluZy1vdmFyaWFuLWZpbmFsLnBuZykgCjwvY2VudGVyPgoKIyBFeHBvcnRpbmcgTmV0d29ya3MKQ3l0b3NjYXBlIHByb3ZpZGVzIGEgbnVtYmVyIG9mIHdheXMgdG8gZXhwb3J0IHJlc3VsdHMgYW5kIHZpc3VhbGl6YXRpb25zOgoKLSBBcyBhbiBpbWFnZToKCmBgYHtyfQpleHBvcnRJbWFnZSgnLi9kaWZmZXJlbnRpYWxseS1leHByZXNzZWQtZ2VuZXMnLCAnUERGJykKZXhwb3J0SW1hZ2UoJy4vZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLWdlbmVzJywgJ1BORycpCmV4cG9ydEltYWdlKCcuL2RpZmZlcmVudGlhbGx5LWV4cHJlc3NlZC1nZW5lcycsICdKUEVHJykKZXhwb3J0SW1hZ2UoJy4vZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLWdlbmVzJywgJ1NWRycpCmV4cG9ydEltYWdlKCcuL2RpZmZlcmVudGlhbGx5LWV4cHJlc3NlZC1nZW5lcycsICdQUycpCmBgYAoKLSBUbyBhIHB1YmxpYyByZXBvc2l0b3J5OgoKYGBgCmV4cG9ydE5ldHdvcmtUb05ERXgoInVzZXIiLCAicGFzc3dvcmQiLCBUUlVFKQpgYGAKCi0gQXMgYSBDeXRvc2NhcGUgSlNPTiBmaWxlOgoKYGBge3J9CmV4cG9ydE5ldHdvcmsoJy4vZGlmZmVyZW50aWFsbHktZXhwcmVzc2VkLWdlbmVzJywgJ2N5anMnKQpgYGAK